import { logger } from './../logger';
var util = require('util');
var lodash = require('lodash');
var moment = require('moment');
var extend = require('extend');
var events = require('events');
var ReadWriteLock = require('rwlock');

var utils = require('../utils');
import { Dao } from "./dao-service";
var DBTimestamp = 'DBTimestamp';

import delis = require("../../../../../common");


function Resources() {
    this.eventEmitter = new events.EventEmitter();
    var self = this;
    Dao.apply(this, ['resources', function (err, db) {
        // 清除5天前的数据
        return db.remove({ timeExpire: { $lte: moment().subtract(3, 'day').valueOf() } }, { multi: true }).then(function (numRemoved) {
            logger.log('resources过期清理了' + numRemoved + '行');
            return db.count({}).then(function (count) {
                logger.log('resources现在的行数:' + count);
                if (count < 10) {
                    self.eventEmitter.emit('resources.toZero');
                }
            });
        });
    }]);
    this.updatePageSize = 50;
}

Resources.prototype.unboundUpdate = function (resId) {
    return this.datastore.get({ id: resId }, true).then(function (resource) {
        return this.datastore.update({ _id: resource._id, $set: { state: 2, timeWhen: DBTimestamp } });
    }.bind(this));
};

Resources.prototype.deleteUpdate = function (resId, noEvent) {
    return this.datastore.get({ id: resId }, true).then(function (resource) {
        if (!noEvent) {
            //删除数据通知外部
            delis.DataSyncEventListener.Current.OnDeleteEvent(this, { id: resource.id, tableName: resource.type });
        }
        return this.datastore.update({ _id: resource._id, $set: { state: 1, timeWhen: DBTimestamp } });
    }.bind(this));
};

Resources.prototype.putUpdate = function (newResource, noSendNotification, noEvent) {
    return this.datastore.get({ id: newResource.id }).then(function (resource) {
        var oldState = lodash.get(resource, 'state', 0);
        var _newResource = extend(resource || {}, newResource);
        if (oldState == 1 && _newResource.state != oldState) {
            _newResource.state = oldState;
        }
        return _newResource;
    }).then(function (resource) {
        if (!noSendNotification) {
            resource.timeWhen = DBTimestamp;
        }
        if (!noEvent) {
            //向resource中修改数据 通知外部
            delis.DataSyncEventListener.Current.OnUpdateEvent(this, { newItem: resource });
        }
        return this.datastore.update(resource);
    }.bind(this));
};

Resources.prototype.updateResource = function (update, resData, noSendNotification, noEvent) {
    var resource = extend({}, update, resData ? { data: resData, id: update.updateId } : { id: update.updateId });
    delete resource._id;
    var promise;
    if (noSendNotification) {
        promise = this.datastore.get({ id: resource.id }).then(function (existing) {
            if (existing) {
                resource._id = existing._id;
                resource.timeWhen = existing.timeWhen;
                resource = existing;
                if (!noEvent) {
                    delis.DataSyncEventListener.Current.OnUpdateEvent(this, { newItem: existing });
                }
            }
            else {
                if (!noEvent) {
                    delis.DataSyncEventListener.Current.OnAddDataSyncEvent(this, resource);
                }
            }
        });
    } else {
        promise = Promise.resolve().then(() => {
            if (!noEvent) {
                delis.DataSyncEventListener.Current.OnUpdateEvent(this, { newItem: resource });
            }
        });
        // promise = Promise.resolve();
    }
    return promise.then(function () {
        return this.datastore.update(resource);
    }.bind(this)).then(function () {
        return resource;
    });
};

/**
 * 排除过期时间和终端Id
 * @param timeAfter
 * @param excludedBindId
 * @param expire 过期时间
 * @returns {*}
 */
Resources.prototype.queryAsUpdates = function (lastTime, excludedBindId, expire) {
    Contract.expect(lastTime >= 0);
    function query(criteria, pageSize) {
        return self.datastore.find({ $and: criteria }, undefined, { timeWhen: 1 }, 0, pageSize);
    }

    var _criteria = [];
    var timeExpire = moment().valueOf();
    if (expire) {
        timeExpire = expire;
        if (lodash.isString(expire)) {
            timeExpire = moment(expire, 'YYYYMMDDHHmmssSSS').valueOf();
        }
    }
    _criteria.push({ $or: [{ timeExpire: { $exists: false } }, { timeExpire: null }, { timeExpire: { $gt: timeExpire } }] });

    if (excludedBindId) {
        _criteria.push({ $or: [{ terminalBindId: { $exists: false } }, { terminalBindId: null }, { terminalBindId: { $ne: excludedBindId } }] });
    }

    var self = this;

    if (process.env.NODE_ENV == "development") {
        this.datastore.find({
            $and: [
                { timeWhen: { $gt: lastTime } },
                { $or: [{ timeExpire: { $exists: false } }, { timeExpire: null }, { timeExpire: { $gt: timeExpire } }] },
                { $or: [{ terminalBindId: { $exists: false } }, { terminalBindId: null }, { terminalBindId: { $ne: excludedBindId } }] }
            ]
        }).then(function (r) {
            // console.log(r);
            //console.log(r.length);
        });
    }

    return query([{ timeWhen: { $gt: lastTime } }].concat(_criteria), self.updatePageSize).then(function (resources) {
        if (!resources.length) {
            return resources;
        }
        return query([{ timeWhen: resources[resources.length - 1].timeWhen }].concat(_criteria), 0).then(function (resources2) {
            return lodash.uniq(resources.concat(resources2 || []), 'id');
        });
    }).then(function (resources) {
        var urls = resources.map(function (resource) {
            return lodash.omit(resource, ['_id', 'timeWhen', 'data', 'version']);
        });
        return { urls: urls, lastSyncTime: resources.length ? moment(resources[resources.length - 1].timeWhen).format('YYYYMMDDHHmmssSSS') : undefined };
    });
};

function hasKey(objs, key) {
    return lodash.reject(objs, lodash.property(key)).length == 0;
}

Resources.prototype.getSinglePackageAsUpdate = function (resEntity, resId) {
    var self = this;
    return this.getAsUpdate(resEntity, resId).then(function (resource) {
        if (resource && resource.data && resource.data.resources && hasKey(resource.data.resources, 'id')) {
            return Promise.all(resource.data.resources.map(function (child) {
                return self.getAsUpdate(child.type, child.id).then(function (_resource) {
                    if (!_resource || !_resource.data) {
                        logger.error('包内的资源' + child.id + '竟然不存在了', _resource);
                        throw '包内的资源' + child.id + '竟然不存在了';
                    }
                    return {
                        id: _resource.id,
                        type: _resource.type,
                        uri: _resource.uri,
                        state: _resource.state,
                        resourceVersion: _resource.version,
                        timeExpire: _resource.timeExpire,
                        obj: _resource.data
                    };
                });
            })).then(function (children) {
                resource.data.children = children;
                return resource;
            }).catch(function (err) {
                return null;
            });
        }
        return resource;
    });
};

Resources.prototype.getAsUpdate = function (resEntity, resId) {
    return this.datastore.get({ id: resId }).then(function (resource) {
        if (!resource || resource.timeExpire < new Date().getTime()) {
            console.log(resId + '的资源不存在');
            return;
        }
        if (lodash.get(resource, 'data.extended.version.stale')) {
            delete resource.data.extended.version;
        }
        return resource;
    });
};

Resources.prototype.isOutOfVersion = function (resId, version) {
    return this.datastore.count({ $and: [{ id: resId }, { version: { $gt: version } }] }).then(function (count) {
        return count > 0;
    });
};

/**
 * 只能作逻辑删除
 * @param update
 * @returns {*}
 */
Resources.prototype.logicDeleteById = function (update, noEvent) {
    var self = this;
    return self.datastore.get({ id: update.updateId }).then(function (resource) {
        if (resource) {
            var _id = resource._id;
            resource = extend(resource, update);
            resource._id = _id;
            resource.id = update.updateId;
            delete resource.data;
        } else {
            resource = extend({}, update, { id: update.updateId });
            delete resource._id;
        }
        resource.state = 1;
        resource.timeWhen = DBTimestamp;
        if (!noEvent) {
            //删除数据通知外部
            delis.DataSyncEventListener.Current.OnDeleteEvent(this, { id: resource.id, tableName: resource.type });
        }
        return self.datastore.update(resource);
    });
};

Resources.prototype.lock = function (resourceVersions, newTerminalId, newUserName, syncInfo) {
    return this.lockReturnFull(resourceVersions, newTerminalId, newUserName, syncInfo, false).then(function (ret) {
        ret.versions = ret.versions.map(function (version) {
            return version.version;
        });
        return ret;
    });
}

Resources.prototype.unlock = function (resourceVersions, newTerminalId) {
    return this.unlockReturnFull(resourceVersions, newTerminalId).then(function (ret) {
        ret.versions = ret.versions.map(function (version) {
            return version.version;
        });
        return ret;
    });
}

Resources.prototype.check = function (resourceVersions, newTerminalId, newUserName, syncInfo) {
    return this.lockReturnFull(resourceVersions, newTerminalId, newUserName, syncInfo, true).then(function (ret) {
        ret.versions = ret.versions.map(function (version) {
            return version.version;
        });
        return ret;
    });
}

var lock = new ReadWriteLock();
var autoUnLockDealy = 4 * 3600 * 1000;
function initExtended(obj) {
    if (!obj) return;
    if (!obj.extended) {
        delete obj.extended;
    }
    lodash.defaultsDeep(obj, { extended: { version: { version: 0 } } });
    if (!lodash.get(obj, 'extended.version')) {
        delete obj.extended.version;
        lodash.defaultsDeep(obj, { extended: { version: { version: 0 } } });
    }
}
Resources.prototype.initExtended = initExtended;
Resources.prototype.lockReturnFull = function (resourceVersions, newTerminalId, newUserName, syncInfo, isCheck) {
    var self = this;
    return (<any>Promise).promisify(lock.async.writeLock)().then(function (release) {
        function modifyResource(resource) {
            resource.data.extended.version.version++;
            lodash.extend(resource.data.extended.version, {
                userName: newUserName,
                terminalId: newTerminalId,
                stale: new Date().getTime()
            });
            return resource;
        }

        return (<any>Promise.all(resourceVersions.map(function (resourceVersion) {
            return self.datastore.get({ id: resourceVersion.id }).then(function (resource) {
                if (!resource || !resource.data) {
                    return modifyResource({
                        id: resourceVersion.id, data: {
                            id: resourceVersion.id, extended: {
                                version: {
                                    version: 0
                                }
                            }
                        }
                    });
                }
                // var lastVersion = null;
                initExtended(resource.data);
                
                // lastVersion = resource.data.extended.version;
                // var currVersion = resourceVersion.version || 0;
                // var currentTime = new Date().getTime();
                // if (currVersion == lastVersion.version) {
                //     return modifyResource(resource);
                // } else if (lastVersion.version == 0) {
                //     resource.data.extended.version.version = currVersion;
                //     return modifyResource(resource);
                // } else if (newTerminalId == lastVersion.terminalId) {
                //     return modifyResource(resource);
                //     //两个小时进行解锁
                // } else if (lastVersion.stale && (currentTime > lastVersion.stale + autoUnLockDealy)) {
                //     return modifyResource(resource);
                // }
                // else if (resource.terminalBindId == "fbs") {
                //     //如果是来自fbs的数据，直接解锁。
                //     return modifyResource(resource);
                // }
                // else {
                //     var err = { conflicts: {} };
                //     err.conflicts[resourceVersion.id] = resource.data.extended.version;
                //     throw err;
                // }

                //FBS没有锁的问题，所以直接调用
                return modifyResource(resource);
            });
        })).catch(function (err) {
            if (!lodash.get(syncInfo, 'lastSyncTime'))
                throw err;
            return self.queryAsUpdates(moment(syncInfo.lastSyncTime, 'YYYYMMDDHHmmssSSS').valueOf(), syncInfo.excludedBindId).then(function (updates) {
                err.updates = updates;
                throw err;
            });
        }).then(function (modifiedResources) {
            return Promise.all(modifiedResources.map(function (modifiedResource: any) {
                var promise = Promise.resolve();
                if (!isCheck) {
                    promise = self.datastore.update(modifiedResource);
                }
                return promise.then(function () {
                    return modifiedResource.data.extended.version
                });
            })).then(function (result) {
                return { versions: result };
            });
        })).finally(release);
    });
};

Resources.prototype.unlockReturnFull = function (_resourceVersions, newTerminalId) {
    var self = this;
    var resourceVersions = _resourceVersions.resourceVersions;
    var packageObj = _resourceVersions.packageObj;
    return (<any>Promise).promisify(lock.async.writeLock)().then(function (release) {
        return (<any>Promise.all(resourceVersions.map(function (resourceVersion) {
            return self.datastore.get({ id: resourceVersion.id }, true).then(function (resource) {
                initExtended(resource.data);
                if (resource.data.extended.version.stale && resource.data.extended.version.terminalId == newTerminalId) {
                    delete resource.data.extended.version.stale;
                    resource.data.extended.version.version = resource.data.extended.version.version - 1;
                    return resource;
                } else {
                    var err = { conflicts: {} };
                    err.conflicts[resourceVersion.id] = resource.data.extended.version;
                    throw err;
                }
            });
        })).then(function (modifiedResources) {
            return Promise.all(modifiedResources.map(function (modifiedResource: any) {
                return self.datastore.update(modifiedResource).then(function () {
                    return modifiedResource.data.extended.version
                });
            })).then(function (result) {
                //做一个包
                var pkg = {
                    id: packageObj.obj.id,
                    updateId: packageObj.obj.id,
                    state: 0,
                    uri: packageObj.uri,
                    type: 'sync-packages',
                    timeExpire: packageObj.timeExpire,
                    terminalBindId: packageObj.excludedBindId,
                    timeWhen: DBTimestamp,
                    data: extend({}, packageObj.obj, {
                        resources: lodash.map(resourceVersions, function (resourceVersion) {
                            return lodash.pick(resourceVersion, ['id', 'type', 'uri']);
                        })
                    })
                };
                return self.datastore.update(pkg).then(function () {
                    return { versions: result }
                });
            });
        })).finally(release);
    });
};

module.exports = new Resources();